package com.example.ch01wwwssocom.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * @Description: jwt工具类
 * @Author: wes
 * @Date: 2021/1/5 14:18
 */
@Slf4j
public class JwtUtil {

    private static final String iss = "server";//签发者
    private static final String default_hs256_secret = "be1672c0fce74e42aaff91a9398c64ec"; //hs256 设置的默认秘钥


    /**
     * hs256 的算法实现
     *
     * @param secret 秘钥
     * @return
     */
    public static Algorithm hs256(String secret) {

        if (secret == null) {
            return Algorithm.HMAC256(default_hs256_secret);
        }
        return Algorithm.HMAC256(secret);
    }

    /**
     * pem文件，需利用 OpenSSL生成私钥和公钥 工具生成
     * <p>
     * rs256 的算法实现
     *
     * @param publicKeyPemPath  公钥pem文件路径
     * @param privateKeyPemPath 私钥pem文件路径
     * @return
     */
    public static Algorithm rs256(String publicKeyPemPath, String privateKeyPemPath) {


        RSAPublicKey rsaPublicKey = null;
        RSAPrivateKey rsaPrivateKey = null;
        try {
            rsaPublicKey = publicKeyPemPath == null ? null : (RSAPublicKey) getPublicKey(publicKeyPemPath);
            rsaPrivateKey = privateKeyPemPath == null ? null : (RSAPrivateKey) getPrivateKey(privateKeyPemPath);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("rs256, 发生异常", e);
        }

        return Algorithm.RSA256(rsaPublicKey, rsaPrivateKey);
    }

    public static PrivateKey getPrivateKey(String filename) throws Exception {


        File f = new File(filename);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        // Remove the first and last lines
        String keyStr = new String(keyBytes).replace("-----BEGIN PRIVATE KEY-----", "");
        keyStr = keyStr.replace("-----END PRIVATE KEY-----", "");
        keyStr = keyStr.trim();
        log.info(keyStr);

        byte[] encoded = Base64.getMimeDecoder().decode(keyStr);

        // PKCS8 decode the encoded RSA private key
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privKey = kf.generatePrivate(keySpec);

        return privKey;
    }

    public static PublicKey getPublicKey(String filename) throws Exception {

        File f = new File(filename);
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        byte[] keyBytes = new byte[(int) f.length()];
        dis.readFully(keyBytes);
        dis.close();

        // Remove the first and last lines
        String keyStr = new String(keyBytes).replace("-----BEGIN PUBLIC KEY-----", "");
        keyStr = keyStr.replace("-----END PUBLIC KEY-----", "");
        keyStr = keyStr.trim();
        log.info(keyStr);
        // Base64 decode the data
        byte[] encoded = Base64.getMimeDecoder().decode(keyStr);

        // PKCS8 decode the encoded RSA private key
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey publicKey = kf.generatePublic(keySpec);

        // Display the results
        System.out.println(publicKey);

        return publicKey;
    }


    /**
     * @param aud      接收者
     * @param alg      算法
     * @param nbf      token在 nbf 之前，不能进行token解析处理
     * @param exp      token失效 时间
     * @param sub      token主题
     * @param jti      token唯一标识
     * @param claimMap payload 追加数据，这里的数据是外界可见的，建议添加非敏感信息
     * @return token
     */
    public static String createToken(String aud, Algorithm alg, Date nbf,
                                     Date exp, String sub, String jti,
                                     Map<String, String> claimMap) {
        //token 由 header.payload.sign 构成

        HashMap<String, Object> headers = new HashMap<>();
        headers.put("typ", "JWT"); //类型

        //签发日期
        Date issuedAtTime = Calendar.getInstance().getTime();

        //生成构造
        JWTCreator.Builder builder = JWT.create()
                .withHeader(headers) //头部信息
                .withIssuer(iss) //创建者 iss
                .withAudience(aud) //颁发给客户端、或者其他使用者 aud
                .withJWTId(jti) //令牌的唯一的标识 jti
                .withSubject(sub) //令牌主题 sub
                .withNotBefore(nbf) //claim标识了时间点，当早于这个时间点，JWT不会被接受和处理 nbf
                .withIssuedAt(issuedAtTime) //令牌颁发日期 iat
                .withExpiresAt(exp);//令牌失效日期 exp

        //添加一些自定义的payload, claim
        Iterator<Map.Entry<String, String>> iterator = claimMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, String> next = iterator.next();
            builder.withClaim(next.getKey(), next.getValue());
        }

        return builder.sign(alg);
    }


    /**
     * 校验 token, 并且取值
     *
     * @param alg   算法
     * @param token 令牌
     * @return 解码后的jwt对象
     * @throws TokenExpiredException          token过期异常
     * @throws AlgorithmMismatchException     算法不正确异常
     * @throws InvalidClaimException          早于nbf时间解析，会抛出这样的异常
     * @throws SignatureVerificationException 签名验证异常
     * @throws JWTDecodeException             编码不正确异常
     * @throws JWTVerificationException       token验证失败异常，是父级异常
     */
    public static DecodedJWT verifyToken(Algorithm alg, String token) throws JWTVerificationException {

        JWTVerifier jwtVerifier = JWT.require(alg).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        return decodedJWT;
    }


    /**
     * 解析token, 不进行认证，不考虑过期
     * @param token
     * @return
     */
    public static DecodedJWT getDecodedJWT(String token) {

        DecodedJWT decode = null;
        try {
            decode = JWT.decode(token);
        }catch (JWTDecodeException e){
            e.printStackTrace();
        }

        return decode;
    }
}
